package repository

import (
	"errors"
	"reflect"
	"strings"
)

func Copy(from interface{}, to interface{}) error {

	tTypeA := reflect.TypeOf(from)
	tValueA := reflect.ValueOf(from)
	if tTypeA.Kind() == reflect.Ptr {
		tTypeA = tTypeA.Elem()
		tValueA = tValueA.Elem()
	}

	tTypeB := reflect.TypeOf(to)
	if tTypeB.Kind() != reflect.Ptr {
		return errors.New("can't copy to  value data")
	}
	tTypeB = tTypeB.Elem()
	tValueB := reflect.ValueOf(to).Elem()

	if tTypeA.NumField() > tTypeB.NumField() {
		for i := 0; i < tTypeA.NumField(); i++ {
			fieldName := tTypeA.Field(i).Name
			fieldB, ok := tTypeB.FieldByName(fieldName)
			if ok {
				fieldAValue := tValueA.FieldByName(fieldB.Name)
				if !fieldAValue.IsValid() {
					continue
				}
				if fieldAValue.Type().PkgPath() == "database/sql" && strings.Contains(fieldAValue.Type().Name(), "Null") {
					numField := fieldAValue.Type().NumField()
					for j := 0; j < numField; j++ {
						field := fieldAValue.Type().Field(j)
						if field.Name == "Valid" {
							valid := fieldAValue.FieldByName("Valid").Interface().(bool)
							if !valid {
								break
							}
						} else {
							data := fieldAValue.Field(j)
							tValueB.FieldByName(fieldB.Name).Set(data)
						}
					}
				} else if tValueB.FieldByName(fieldB.Name).Type().PkgPath() == "database/sql" && strings.Contains(tValueB.FieldByName(fieldB.Name).Type().Name(), "Null") {
					numField := tValueB.FieldByName(fieldB.Name).Type().NumField()
					for j := 0; j < numField; j++ {
						field := tValueB.FieldByName(fieldB.Name).Type().Field(j)
						if field.Name != "Valid" && !fieldAValue.IsZero() {
							tValueB.FieldByName(fieldB.Name).Field(j).Set(fieldAValue)
							tValueB.FieldByName(fieldB.Name).FieldByName("Valid").SetBool(true)
						}
					}
				} else {
					tValueB.FieldByName(fieldB.Name).Set(fieldAValue)
				}
			}
		}
	} else {
		for i := 0; i < tTypeB.NumField(); i++ {
			fieldName := tTypeB.Field(i).Name
			fieldB, ok := tTypeB.FieldByName(fieldName)
			if ok {
				fieldAValue := tValueA.FieldByName(fieldB.Name)
				if !fieldAValue.IsValid() {
					continue
				}
				if fieldAValue.Type().PkgPath() == "database/sql" && strings.Contains(fieldAValue.Type().Name(), "Null") {
					numField := fieldAValue.Type().NumField()
					for j := 0; j < numField; j++ {
						field := fieldAValue.Type().Field(j)
						if field.Name == "Valid" {
							valid := fieldAValue.FieldByName("Valid").Interface().(bool)
							if !valid {
								break
							}
						} else {
							data := fieldAValue.Field(j)
							tValueB.FieldByName(fieldB.Name).Set(data)
						}
					}
				} else if tValueB.FieldByName(fieldB.Name).Type().PkgPath() == "database/sql" && strings.Contains(tValueB.FieldByName(fieldB.Name).Type().Name(), "Null") {
					numField := tValueB.FieldByName(fieldB.Name).Type().NumField()
					for j := 0; j < numField; j++ {
						field := tValueB.FieldByName(fieldB.Name).Type().Field(j)
						if field.Name != "Valid" && !fieldAValue.IsZero() {
							tValueB.FieldByName(fieldB.Name).Field(j).Set(fieldAValue)
							tValueB.FieldByName(fieldB.Name).FieldByName("Valid").SetBool(true)
						}
					}
				} else {
					tValueB.FieldByName(fieldB.Name).Set(fieldAValue)
				}
			}
		}
	}

	return nil
}

func CopySlice[M any, N any](sliceA []M, sliceB []N) error {
	for i, item := range sliceA {
		var b = sliceB[i]
		err := Copy(item, &b)
		if err != nil {
			return err
		}
		sliceB[i] = b
	}
	return nil
}
